home *** CD-ROM | disk | FTP | other *** search
/ Computer Select (Limited Edition) / Computer Select.iso / dobbs / v17n02 / graphprg.asc < prev    next >
Encoding:
Text File  |  1991-12-17  |  18.4 KB  |  437 lines

  1. _GRAPHICS PROGRAMMING COLUMN_
  2. by Michael Abrash
  3.  
  4.  
  5.  
  6. [LISTING ONE]
  7.  
  8.  
  9. /* 3D animation program to view a cube as it rotates in mode X. The viewpoint 
  10. is fixed at the origin (0,0,0) of world space, looking in the direction of 
  11. increasingly negative Z. A right-handed coordinate system is used throughout.
  12. All C code tested with Borland C++ 2.0 in C compilation mode */
  13. #include <conio.h>
  14. #include <dos.h>
  15. #include <math.h>
  16. #include "polygon.h"
  17.  
  18. #define ROTATION  (M_PI / 30.0)  /* rotate by 6 degrees at a time */
  19.  
  20. /* Base offset of page to which to draw */
  21. unsigned int CurrentPageBase = 0;
  22. /* Clip rectangle; clips to the screen */
  23. int ClipMinX=0, ClipMinY=0;
  24. int ClipMaxX=SCREEN_WIDTH, ClipMaxY=SCREEN_HEIGHT;
  25. /* Rectangle specifying extent to be erased in each page */
  26. struct Rect EraseRect[2] = { {0, 0, SCREEN_WIDTH, SCREEN_HEIGHT},
  27.    {0, 0, SCREEN_WIDTH, SCREEN_HEIGHT} };
  28. static unsigned int PageStartOffsets[2] =
  29.    {PAGE0_START_OFFSET,PAGE1_START_OFFSET};
  30. int DisplayedPage, NonDisplayedPage;
  31. /* Transformation from cube's object space to world space. Initially
  32.    set up to perform no rotation and to move the cube into world
  33.    space -100 units away from the origin down the Z axis. Given the
  34.    viewing point, -100 down the Z axis means 100 units away in the
  35.    direction of view. The program dynamically changes both the
  36.    translation and the rotation. */
  37. static double CubeWorldXform[4][4] = {
  38.    {1.0, 0.0, 0.0, 0.0},
  39.    {0.0, 1.0, 0.0, 0.0},
  40.    {0.0, 0.0, 1.0, -100.0},
  41.    {0.0, 0.0, 0.0, 1.0} };
  42. /* Transformation from world space into view space. Because in this
  43.    application the view point is fixed at the origin of world space,
  44.    looking down the Z axis in the direction of increasing Z, view space is
  45.    identical to world space, and this is the identity matrix */
  46. static double WorldViewXform[4][4] = {
  47.    {1.0, 0.0, 0.0, 0.0},
  48.    {0.0, 1.0, 0.0, 0.0},
  49.    {0.0, 0.0, 1.0, 0.0},
  50.    {0.0, 0.0, 0.0, 1.0}
  51. };
  52. /* All vertices in the cube */
  53. static struct Point3 CubeVerts[] = {
  54.    {15,15,15,1},{15,15,-15,1},{15,-15,15,1},{15,-15,-15,1},
  55.    {-15,15,15,1},{-15,15,-15,1},{-15,-15,15,1},{-15,-15,-15,1}};
  56. /* Vertices after transformation */
  57. static struct Point3
  58.       XformedCubeVerts[sizeof(CubeVerts)/sizeof(struct Point3)];
  59. /* Vertices after projection */
  60. static struct Point3
  61.       ProjectedCubeVerts[sizeof(CubeVerts)/sizeof(struct Point3)];
  62. /* Vertices in screen coordinates */
  63. static struct Point
  64.       ScreenCubeVerts[sizeof(CubeVerts)/sizeof(struct Point3)];
  65. /* Vertex indices for individual faces */   
  66. static int Face1[] = {1,3,2,0};
  67. static int Face2[] = {5,7,3,1};
  68. static int Face3[] = {4,5,1,0};
  69. static int Face4[] = {3,7,6,2};
  70. static int Face5[] = {5,4,6,7};
  71. static int Face6[] = {0,2,6,4};
  72. /* List of cube faces */
  73. static struct Face CubeFaces[] = {{Face1,4,15},{Face2,4,14},
  74.    {Face3,4,12},{Face4,4,11},{Face5,4,10},{Face6,4,9}};
  75. /* Master description for cube */
  76. static struct Object Cube = {sizeof(CubeVerts)/sizeof(struct Point3),
  77.    CubeVerts, XformedCubeVerts, ProjectedCubeVerts, ScreenCubeVerts,
  78.    sizeof(CubeFaces)/sizeof(struct Face), CubeFaces};
  79.  
  80. void main() {
  81.    int Done = 0, RecalcXform = 1;
  82.    double WorkingXform[4][4];
  83.    union REGS regset;
  84.  
  85.    /* Set up the initial transformation */
  86.    Set320x240Mode(); /* set the screen to mode X */
  87.    ShowPage(PageStartOffsets[DisplayedPage = 0]);
  88.    /* Keep transforming the cube, drawing it to the undisplayed page,
  89.       and flipping the page to show it */
  90.    do {
  91.       /* Regenerate the object->view transformation and
  92.          retransform/project if necessary */
  93.       if (RecalcXform) {
  94.          ConcatXforms(WorldViewXform, CubeWorldXform, WorkingXform);
  95.          /* Transform and project all the vertices in the cube */
  96.          XformAndProjectPoints(WorkingXform, &Cube);
  97.          RecalcXform = 0;
  98.       }
  99.       CurrentPageBase =    /* select other page for drawing to */
  100.             PageStartOffsets[NonDisplayedPage = DisplayedPage ^ 1];
  101.       /* Clear the portion of the non-displayed page that was drawn
  102.          to last time, then reset the erase extent */
  103.       FillRectangleX(EraseRect[NonDisplayedPage].Left,
  104.             EraseRect[NonDisplayedPage].Top,
  105.             EraseRect[NonDisplayedPage].Right,
  106.             EraseRect[NonDisplayedPage].Bottom, CurrentPageBase, 0);
  107.       EraseRect[NonDisplayedPage].Left =
  108.             EraseRect[NonDisplayedPage].Top = 0x7FFF;
  109.       EraseRect[NonDisplayedPage].Right =
  110.             EraseRect[NonDisplayedPage].Bottom = 0;
  111.       /* Draw all visible faces of the cube */
  112.       DrawVisibleFaces(&Cube);
  113.       /* Flip to display the page into which we just drew */
  114.       ShowPage(PageStartOffsets[DisplayedPage = NonDisplayedPage]);
  115.       while (kbhit()) {
  116.          switch (getch()) {
  117.             case 0x1B:     /* Esc to exit */
  118.                Done = 1; break;
  119.             case 'A': case 'a':      /* away (-Z) */
  120.                CubeWorldXform[2][3] -= 3.0; RecalcXform = 1; break;
  121.             case 'T':      /* towards (+Z). Don't allow to get too */
  122.             case 't':      /* close, so Z clipping isn't needed */
  123.                if (CubeWorldXform[2][3] < -40.0) {
  124.                      CubeWorldXform[2][3] += 3.0;
  125.                      RecalcXform = 1;
  126.                }
  127.                break;
  128.             case '4':         /* rotate clockwise around Y */
  129.                AppendRotationY(CubeWorldXform, -ROTATION);
  130.                RecalcXform=1; break;
  131.             case '6':         /* rotate counterclockwise around Y */
  132.                AppendRotationY(CubeWorldXform, ROTATION);
  133.                RecalcXform=1; break;
  134.             case '8':         /* rotate clockwise around X */
  135.                AppendRotationX(CubeWorldXform, -ROTATION);
  136.                RecalcXform=1; break;
  137.             case '2':         /* rotate counterclockwise around X */
  138.                AppendRotationX(CubeWorldXform, ROTATION);
  139.                RecalcXform=1; break;
  140.             case 0:     /* extended code */
  141.                switch (getch()) {
  142.                   case 0x3B:  /* rotate counterclockwise around Z */
  143.                      AppendRotationZ(CubeWorldXform, ROTATION);
  144.                      RecalcXform=1; break;
  145.                   case 0x3C:  /* rotate clockwise around Z */
  146.                      AppendRotationZ(CubeWorldXform, -ROTATION);
  147.                      RecalcXform=1; break;
  148.                   case 0x4B:  /* left (-X) */
  149.                     CubeWorldXform[0][3] -= 3.0; RecalcXform=1; break;
  150.                   case 0x4D:  /* right (+X) */
  151.                     CubeWorldXform[0][3] += 3.0; RecalcXform=1; break;
  152.                   case 0x48:  /* up (+Y) */
  153.                     CubeWorldXform[1][3] += 3.0; RecalcXform=1; break;
  154.                   case 0x50:  /* down (-Y) */
  155.                     CubeWorldXform[1][3] -= 3.0; RecalcXform=1; break;
  156.                   default:
  157.                     break;
  158.                }
  159.                break;
  160.             default:       /* any other key to pause */
  161.                getch(); break;
  162.          }
  163.       }
  164.    } while (!Done);
  165.    /* Return to text mode and exit */
  166.    regset.x.ax = 0x0003;   /* AL = 3 selects 80x25 text mode */
  167.    int86(0x10, ®set, ®set);
  168. }
  169.  
  170.  
  171.  
  172.  
  173. [LISTING TWO]
  174.  
  175.  
  176. /* Transforms all vertices in the specified object into view space, then 
  177. perspective projects them to screen space and maps them to screen coordinates, 
  178. storing the results in the object. */
  179. #include <math.h>
  180. #include "polygon.h"
  181.  
  182. void XformAndProjectPoints(double Xform[4][4],
  183.    struct Object * ObjectToXform)
  184. {
  185.    int i, NumPoints = ObjectToXform->NumVerts;
  186.    struct Point3 * Points = ObjectToXform->VertexList;
  187.    struct Point3 * XformedPoints = ObjectToXform->XformedVertexList;
  188.    struct Point3 * ProjectedPoints =
  189.          ObjectToXform->ProjectedVertexList;
  190.    struct Point * ScreenPoints = ObjectToXform->ScreenVertexList;
  191.  
  192.    for (i=0; i<NumPoints; i++, Points++, XformedPoints++,
  193.          ProjectedPoints++, ScreenPoints++) {
  194.       /* Transform to view space */
  195.       XformVec(Xform, (double *)Points, (double *)XformedPoints);
  196.       /* Perspective-project to screen space */
  197.       ProjectedPoints->X = XformedPoints->X / XformedPoints->Z *
  198.             PROJECTION_RATIO * (SCREEN_WIDTH / 2.0);
  199.       ProjectedPoints->Y = XformedPoints->Y / XformedPoints->Z *
  200.             PROJECTION_RATIO * (SCREEN_WIDTH / 2.0);
  201.       ProjectedPoints->Z = XformedPoints->Z;
  202.       /* Convert to screen coordinates. The Y coord is negated to
  203.          flip from increasing Y being up to increasing Y being down,
  204.          as expected by the polygon filler. Add in half the screen
  205.          width and height to center on the screen */
  206.       ScreenPoints->X = ((int) floor(ProjectedPoints->X + 0.5)) +
  207.                SCREEN_WIDTH/2;
  208.       ScreenPoints->Y = (-((int) floor(ProjectedPoints->Y + 0.5))) +
  209.                SCREEN_HEIGHT/2;
  210.    }
  211. }
  212.  
  213.  
  214.  
  215. [LISTING THREE]
  216.  
  217.  
  218. /* Draws all visible faces (faces pointing toward the viewer) in the specified 
  219. object. The object must have previously been transformed and projected, so 
  220. that the ScreenVertexList array is filled in. */
  221. #include "polygon.h"
  222.  
  223. void DrawVisibleFaces(struct Object * ObjectToXform)
  224. {
  225.    int i, j, NumFaces = ObjectToXform->NumFaces, NumVertices;
  226.    int * VertNumsPtr;
  227.    struct Face * FacePtr = ObjectToXform->FaceList;
  228.    struct Point * ScreenPoints = ObjectToXform->ScreenVertexList;
  229.    long v1,v2,w1,w2;
  230.    struct Point Vertices[MAX_POLY_LENGTH];
  231.    struct PointListHeader Polygon;
  232.  
  233.    /* Draw each visible face (polygon) of the object in turn */
  234.    for (i=0; i<NumFaces; i++, FacePtr++) {
  235.       NumVertices = FacePtr->NumVerts;
  236.       /* Copy over the face's vertices from the vertex list */
  237.       for (j=0, VertNumsPtr=FacePtr->VertNums; j<NumVertices; j++)
  238.          Vertices[j] = ScreenPoints[*VertNumsPtr++];
  239.       /* Draw only if outside face showing (if the normal to the
  240.          polygon points toward the viewer; that is, has a positive
  241.          Z component) */
  242.       v1 = Vertices[1].X - Vertices[0].X;
  243.       w1 = Vertices[NumVertices-1].X - Vertices[0].X;
  244.       v2 = Vertices[1].Y - Vertices[0].Y;
  245.       w2 = Vertices[NumVertices-1].Y - Vertices[0].Y;
  246.       if ((v1*w2 - v2*w1) > 0) {
  247.          /* It is facing the screen, so draw */
  248.          /* Appropriately adjust the extent of the rectangle used to
  249.             erase this page later */
  250.          for (j=0; j<NumVertices; j++) {
  251.             if (Vertices[j].X > EraseRect[NonDisplayedPage].Right)
  252.                if (Vertices[j].X < SCREEN_WIDTH)
  253.                   EraseRect[NonDisplayedPage].Right = Vertices[j].X;
  254.                else EraseRect[NonDisplayedPage].Right = SCREEN_WIDTH;
  255.             if (Vertices[j].Y > EraseRect[NonDisplayedPage].Bottom)
  256.                if (Vertices[j].Y < SCREEN_HEIGHT)
  257.                   EraseRect[NonDisplayedPage].Bottom = Vertices[j].Y;
  258.                else EraseRect[NonDisplayedPage].Bottom=SCREEN_HEIGHT;
  259.             if (Vertices[j].X < EraseRect[NonDisplayedPage].Left)
  260.                if (Vertices[j].X > 0)
  261.                   EraseRect[NonDisplayedPage].Left = Vertices[j].X;
  262.                else EraseRect[NonDisplayedPage].Left = 0;
  263.             if (Vertices[j].Y < EraseRect[NonDisplayedPage].Top)
  264.                if (Vertices[j].Y > 0)
  265.                   EraseRect[NonDisplayedPage].Top = Vertices[j].Y;
  266.                else EraseRect[NonDisplayedPage].Top = 0;
  267.          }
  268.          /* Draw the polygon */
  269.          DRAW_POLYGON(Vertices, NumVertices, FacePtr->Color, 0, 0);
  270.       }
  271.    }
  272. }
  273.  
  274.  
  275.  
  276. [LISTING FOUR]
  277.  
  278.  
  279. /* Routines to perform incremental rotations around the three axes */
  280. #include <math.h>
  281. #include "polygon.h"
  282.  
  283. /* Concatenate a rotation by Angle around the X axis to the transformation in 
  284. XformToChange, placing result back in XformToChange. */
  285. void AppendRotationX(double XformToChange[4][4], double Angle)
  286. {
  287.    double Temp10, Temp11, Temp12, Temp20, Temp21, Temp22;
  288.    double CosTemp = cos(Angle), SinTemp = sin(Angle);
  289.  
  290.    /* Calculate the new values of the four affected matrix entries */
  291.    Temp10 = CosTemp*XformToChange[1][0]+ -SinTemp*XformToChange[2][0];
  292.    Temp11 = CosTemp*XformToChange[1][1]+ -SinTemp*XformToChange[2][1];
  293.    Temp12 = CosTemp*XformToChange[1][2]+ -SinTemp*XformToChange[2][2];
  294.    Temp20 = SinTemp*XformToChange[1][0]+ CosTemp*XformToChange[2][0];
  295.    Temp21 = SinTemp*XformToChange[1][1]+ CosTemp*XformToChange[2][1];
  296.    Temp22 = SinTemp*XformToChange[1][2]+ CosTemp*XformToChange[2][2];
  297.    /* Put the results back into XformToChange */
  298.    XformToChange[1][0] = Temp10; XformToChange[1][1] = Temp11;
  299.    XformToChange[1][2] = Temp12; XformToChange[2][0] = Temp20; 
  300.    XformToChange[2][1] = Temp21; XformToChange[2][2] = Temp22;
  301. }
  302.  
  303. /* Concatenate a rotation by Angle around the Y axis to the transformation in 
  304. XformToChange, placing result back in XformToChange. */
  305. void AppendRotationY(double XformToChange[4][4], double Angle)
  306. {
  307.    double Temp00, Temp01, Temp02, Temp20, Temp21, Temp22;
  308.    double CosTemp = cos(Angle), SinTemp = sin(Angle);
  309.  
  310.    /* Calculate the new values of the four affected matrix entries */
  311.    Temp00 = CosTemp*XformToChange[0][0]+ SinTemp*XformToChange[2][0];
  312.    Temp01 = CosTemp*XformToChange[0][1]+ SinTemp*XformToChange[2][1];
  313.    Temp02 = CosTemp*XformToChange[0][2]+ SinTemp*XformToChange[2][2];
  314.    Temp20 = -SinTemp*XformToChange[0][0]+ CosTemp*XformToChange[2][0];
  315.    Temp21 = -SinTemp*XformToChange[0][1]+ CosTemp*XformToChange[2][1];
  316.    Temp22 = -SinTemp*XformToChange[0][2]+ CosTemp*XformToChange[2][2];
  317.    /* Put the results back into XformToChange */
  318.    XformToChange[0][0] = Temp00; XformToChange[0][1] = Temp01;
  319.    XformToChange[0][2] = Temp02; XformToChange[2][0] = Temp20;
  320.    XformToChange[2][1] = Temp21; XformToChange[2][2] = Temp22;
  321. }
  322.  
  323. /* Concatenate a rotation by Angle around the Z axis to the transformation in 
  324. XformToChange, placing result back in XformToChange. */
  325. void AppendRotationZ(double XformToChange[4][4], double Angle)
  326. {
  327.    double Temp00, Temp01, Temp02, Temp10, Temp11, Temp12;
  328.    double CosTemp = cos(Angle), SinTemp = sin(Angle);
  329.  
  330.    /* Calculate the new values of the four affected matrix entries */
  331.    Temp00 = CosTemp*XformToChange[0][0]+ -SinTemp*XformToChange[1][0];
  332.    Temp01 = CosTemp*XformToChange[0][1]+ -SinTemp*XformToChange[1][1];
  333.    Temp02 = CosTemp*XformToChange[0][2]+ -SinTemp*XformToChange[1][2];
  334.    Temp10 = SinTemp*XformToChange[0][0]+ CosTemp*XformToChange[1][0];
  335.    Temp11 = SinTemp*XformToChange[0][1]+ CosTemp*XformToChange[1][1];
  336.    Temp12 = SinTemp*XformToChange[0][2]+ CosTemp*XformToChange[1][2];
  337.    /* Put the results back into XformToChange */
  338.    XformToChange[0][0] = Temp00; XformToChange[0][1] = Temp01;
  339.    XformToChange[0][2] = Temp02; XformToChange[1][0] = Temp10;
  340.    XformToChange[1][1] = Temp11; XformToChange[1][2] = Temp12;
  341. }
  342.  
  343.  
  344.  
  345. [LISTING FIVE]
  346.  
  347.  
  348. /* POLYGON.H: Header file for polygon-filling code, also includes a number of 
  349. useful items for 3D animation. */
  350.  
  351. #define MAX_POLY_LENGTH 4  /* four vertices is the max per poly */
  352. #define SCREEN_WIDTH 320
  353. #define SCREEN_HEIGHT 240
  354. #define PAGE0_START_OFFSET 0
  355. #define PAGE1_START_OFFSET (((long)SCREEN_HEIGHT*SCREEN_WIDTH)/4)
  356. /* Ratio: distance from viewpoint to projection plane / width of projection 
  357. plane. Defines the width of the field of view. Lower absolute values = wider 
  358. fields of view; higher values = narrower */
  359. #define PROJECTION_RATIO   -2.0 /* negative because visible Z
  360.                                    coordinates are negative */
  361. /* Draws the polygon described by the point list PointList in color Color with 
  362. all vertices offset by (X,Y) */
  363. #define DRAW_POLYGON(PointList,NumPoints,Color,X,Y)          \
  364.    Polygon.Length = NumPoints; Polygon.PointPtr = PointList; \
  365.    FillConvexPolygon(&Polygon, Color, X, Y);
  366.  
  367. /* Describes a single 2D point */
  368. struct Point {
  369.    int X;   /* X coordinate */
  370.    int Y;   /* Y coordinate */
  371. };
  372. /* Describes a single 3D point in homogeneous coordinates */
  373. struct Point3 {
  374.    double X;   /* X coordinate */
  375.    double Y;   /* Y coordinate */
  376.    double Z;   /* Z coordinate */
  377.    double W;
  378. };
  379. /* Describes a series of points (used to store a list of vertices that 
  380. describe a polygon; each vertex is assumed to connect to the two adjacent 
  381. vertices, and the last vertex is assumed to connect to the first) */
  382. struct PointListHeader {
  383.    int Length;                /* # of points */
  384.    struct Point * PointPtr;   /* pointer to list of points */
  385. };
  386. /* Describes beginning and ending X coordinates of a single horizontal line */
  387. struct HLine {
  388.    int XStart; /* X coordinate of leftmost pixel in line */
  389.    int XEnd;   /* X coordinate of rightmost pixel in line */
  390. };
  391. /* Describes a Length-long series of horizontal lines, all assumed to be on 
  392. contiguous scan lines starting at YStart and proceeding downward (describes
  393. a scan-converted polygon to low-level hardware-dependent drawing code) */
  394. struct HLineList {
  395.    int Length;                /* # of horizontal lines */
  396.    int YStart;                /* Y coordinate of topmost line */
  397.    struct HLine * HLinePtr;   /* pointer to list of horz lines */
  398. };
  399. struct Rect { int Left, Top, Right, Bottom; };
  400. /* Structure describing one face of an object (one polygon) */
  401. struct Face {
  402.    int * VertNums;   /* pointer to vertex ptrs */
  403.    int NumVerts;     /* # of vertices */
  404.    int Color;        /* polygon color */
  405. };
  406. /* Structure describing an object */
  407. struct Object {
  408.    int NumVerts;
  409.    struct Point3 * VertexList;
  410.    struct Point3 * XformedVertexList;
  411.    struct Point3 * ProjectedVertexList;
  412.    struct Point * ScreenVertexList;
  413.    int NumFaces;
  414.    struct Face * FaceList;
  415. };
  416.  
  417. extern void XformVec(double Xform[4][4], double * SourceVec,
  418.    double * DestVec);
  419. extern void ConcatXforms(double SourceXform1[4][4],
  420.    double SourceXform2[4][4], double DestXform[4][4]);
  421. extern void XformAndProjectPoly(double Xform[4][4],
  422.    struct Point3 * Poly, int PolyLength, int Color);
  423. extern int FillConvexPolygon(struct PointListHeader *, int, int, int);
  424. extern void Set320x240Mode(void);
  425. extern void ShowPage(unsigned int StartOffset);
  426. extern void FillRectangleX(int StartX, int StartY, int EndX,
  427.    int EndY, unsigned int PageBase, int Color);
  428. extern void XformAndProjectPoints(double Xform[4][4],
  429.    struct Object * ObjectToXform);
  430. extern void DrawVisibleFaces(struct Object * ObjectToXform);
  431. extern void AppendRotationX(double XformToChange[4][4], double Angle);
  432. extern void AppendRotationY(double XformToChange[4][4], double Angle);
  433. extern void AppendRotationZ(double XformToChange[4][4], double Angle);
  434. extern int DisplayedPage, NonDisplayedPage;
  435. extern struct Rect EraseRect[];
  436.  
  437.